package visuals;

import options.Options;
import song.Song;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

/**
 * Created by IntelliJ IDEA.
 * User: Aleksey
 * Date: 03.01.2008
 * Time: 1:51:04
 */
public class CenterPanel extends JPanel {
    private final static Integer RECT_SIZE = 24;

    private final static Color[] DRUM_COLORS = {
            Color.YELLOW,
            Color.BLUE,
            Color.GREEN,
            Color.LIGHT_GRAY,
            Color.CYAN,
            Color.ORANGE,
            Color.MAGENTA,
            Color.PINK,
            Color.RED,
            Color.WHITE
    };

    private final static Color[] STYLE_COLORS = {
            new Color(0xFFFFFF),
            new Color(0xCCCCCC),
            new Color(0x999999),
            new Color(0x777777),
            new Color(0x555555),
            new Color(0x333333),
            new Color(0x111111)
    };

    private Image layoutImage;
    private Integer currentBar;
    private Integer currentDrum;
    private Integer currentPart;
    private int lastKeyCode = 0;

    public Integer getCurrentBar() {
        return currentBar;
    }

    public Integer getCurrentDrum() {
        return currentDrum;
    }

    public Integer getCurrentPart() {
        return currentPart;
    }

    public void setCurrentPart(Integer currentPart) {
        this.currentPart = currentPart;
    }

    public void setCurrentBar(Integer currentBar) {
        this.currentBar = currentBar;
    }

    public void setCurrentDrum(Integer currentDrum) {
        this.currentDrum = currentDrum;
    }

    public CenterPanel() {
        super();
        setBackground(Color.BLACK);
        layoutImage = new BufferedImage(RECT_SIZE*40 + 10, RECT_SIZE*40 + 10, BufferedImage.TYPE_INT_RGB);
        currentBar = 0;
        currentDrum = 0;
        currentPart = 0;

        addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                super.keyPressed(e);
                final int CTRL_MASK = KeyEvent.CTRL_MASK;
                final int CTRL_SHIFT_MASK = KeyEvent.CTRL_MASK | KeyEvent.SHIFT_MASK;
                final int ALT_MASK = KeyEvent.ALT_MASK;

                switch (e.getKeyCode()) {
                    case KeyEvent.VK_LEFT :
                        switch (e.getModifiers()) {
                            case CTRL_MASK : ActionController.selectPreviousBar(1); break;
                            case CTRL_SHIFT_MASK : ActionController.selectPreviousBar(10); break;
                            case ALT_MASK : ActionController.shiftNotesLeft(); break;
                            default : ActionController.cursorLeft();
                        }
                        break;
                    case KeyEvent.VK_RIGHT :
                        switch (e.getModifiers()) {
                            case CTRL_MASK : ActionController.selectNextBar(1); break;
                            case CTRL_SHIFT_MASK : ActionController.selectNextBar(10); break;
                            case ALT_MASK : ActionController.shiftNotesRight(); break;
                            default : ActionController.cursorRight();
                        }
                        break;
                    case KeyEvent.VK_UP :
                        ActionController.cursorUp();
                        break;
                    case KeyEvent.VK_DOWN :
                        ActionController.cursorDown();
                        break;
                    case KeyEvent.VK_ADD :
                        ActionController.unitePart();
                        break;
                    case KeyEvent.VK_SUBTRACT :
                        ActionController.dividePart();
                        break;
                    case KeyEvent.VK_PAGE_UP :
                        switch (e.getModifiers()) {
                            case CTRL_MASK : ActionController.loudnessUp(currentDrum); break;
                            case CTRL_SHIFT_MASK : ActionController.allLoudnessUp(); break;
                            default : ActionController.styleUp();
                        }
                        break;
                    case KeyEvent.VK_PAGE_DOWN :
                        switch (e.getModifiers()) {
                            case CTRL_MASK : ActionController.loudnessDown(currentDrum); break;
                            case CTRL_SHIFT_MASK : ActionController.allLoudnessDown(); break;
                            default : ActionController.styleDown();
                        }
                        break;
                    case KeyEvent.VK_DELETE :
                        switch (e.getModifiers()) {
                            case CTRL_MASK : ActionController.clearBar(); break;
                            case CTRL_SHIFT_MASK : ActionController.clearFullBar(); break;
                        }
                        break;
                    case KeyEvent.VK_SPACE :
                        ActionController.addOrRemoveNote();
                        break;
                    case KeyEvent.VK_C :
                        switch (e.getModifiers()) {
                            case CTRL_MASK : ActionController.copyNoteAllocation(); break;
                            case CTRL_SHIFT_MASK : ActionController.copyBar(); break;
                        }
                        break;
                    case KeyEvent.VK_V :
                        switch (e.getModifiers()) {
                            case CTRL_MASK : ActionController.pasteNoteAllocation(); break;
                            case CTRL_SHIFT_MASK : ActionController.pasteBar(); break;
                        }
                        break;
                    case KeyEvent.VK_S :
                        switch (e.getModifiers()) {
                            case CTRL_MASK : ActionController.saveTrack(); break;
                        }
                        break;
                    case KeyEvent.VK_N :
                        switch (e.getModifiers()) {
                            case CTRL_MASK : ActionController.addNewBarToTheEnd(); break;
                        }
                        break;
                    case KeyEvent.VK_A :
                        switch (e.getModifiers()) {
                            case ALT_MASK : if (lastKeyCode == 0) {
                                                ActionController.fillEachNote(1);
                                            } break;
                        }
                        break;
                    case KeyEvent.VK_1 :
                        switch (e.getModifiers()) {
                            case CTRL_MASK : lastKeyCode = e.getKeyCode(); break;
                            case CTRL_SHIFT_MASK : lastKeyCode = e.getKeyCode(); break;
                            case ALT_MASK : lastKeyCode = e.getKeyCode(); break;
                        }
                        break;
                    case KeyEvent.VK_2 :
                        switch (e.getModifiers()) {
                            case CTRL_MASK :        if (lastKeyCode == KeyEvent.VK_3) {
                                                        ActionController.detalizeBar(32);
                                                    }
                                                    else {
                                                        if (lastKeyCode == 0){
                                                            ActionController.detalizeBar(2);
                                                        }
                                                    } break;
                            case CTRL_SHIFT_MASK :  if (lastKeyCode == KeyEvent.VK_3) {
                                                        ActionController.detalizeFullBar(32);
                                                    }
                                                    else {
                                                        if (lastKeyCode == 0){
                                                            ActionController.detalizeFullBar(2);
                                                        }
                                                    } break;
                            case ALT_MASK :         if (lastKeyCode == KeyEvent.VK_3) {
                                                        ActionController.fillEachNote(32);
                                                    }
                                                    else {
                                                        if (lastKeyCode == 0){
                                                            ActionController.fillEachNote(2);
                                                        }
                                                    } break;
                        }
                        break;
                    case KeyEvent.VK_3 :
                        switch (e.getModifiers()) {
                            case CTRL_MASK : lastKeyCode = e.getKeyCode(); break;
                            case CTRL_SHIFT_MASK : lastKeyCode = e.getKeyCode(); break;
                            case ALT_MASK : lastKeyCode = e.getKeyCode(); break;
                        }
                        break;
                    case KeyEvent.VK_4 :
                        switch (e.getModifiers()) {
                            case CTRL_MASK :        if (lastKeyCode == KeyEvent.VK_6) {
                                                        ActionController.detalizeBar(64);
                                                    }
                                                    else {
                                                        if (lastKeyCode == 0){
                                                            ActionController.detalizeBar(4);
                                                        }
                                                    } break;
                            case CTRL_SHIFT_MASK :  if (lastKeyCode == KeyEvent.VK_6) {
                                                        ActionController.detalizeFullBar(64);
                                                    }
                                                    else {
                                                        if (lastKeyCode == 0){
                                                            ActionController.detalizeFullBar(4);
                                                        }
                                                    } break;
                            case ALT_MASK :         if (lastKeyCode == KeyEvent.VK_6) {
                                                        ActionController.fillEachNote(64);
                                                    }
                                                    else {
                                                        if (lastKeyCode == 0){
                                                            ActionController.fillEachNote(4);
                                                        }
                                                    } break;
                        }
                        break;
                    case KeyEvent.VK_6 :
                        switch (e.getModifiers()) {
                            case CTRL_MASK :        if (lastKeyCode == KeyEvent.VK_1) {
                                                        ActionController.detalizeBar(16);
                                                    }
                                                    else {
                                                        lastKeyCode = e.getKeyCode();
                                                    } break;
                            case CTRL_SHIFT_MASK :  if (lastKeyCode == KeyEvent.VK_1) {
                                                        ActionController.detalizeFullBar(16);
                                                    }
                                                    else {
                                                        lastKeyCode = e.getKeyCode();
                                                    } break;
                            case ALT_MASK :         if (lastKeyCode == KeyEvent.VK_1) {
                                                        ActionController.fillEachNote(16);
                                                    }
                                                    else {
                                                        lastKeyCode = e.getKeyCode();
                                                    } break;
                        }
                        break;
                    case KeyEvent.VK_8 :
                        switch (e.getModifiers()) {
                            case CTRL_MASK :        if (lastKeyCode == 0){
                                                        ActionController.detalizeBar(8);
                                                    } break;
                            case CTRL_SHIFT_MASK :  if (lastKeyCode == 0){
                                                        ActionController.detalizeFullBar(8);
                                                    } break;
                            case ALT_MASK :         if (lastKeyCode == 0){
                                                        ActionController.fillEachNote(8);
                                                    } break;
                        }
                        break;
                }
            }

            public void keyReleased(KeyEvent e) {
                super.keyReleased(e);
                if (e.getKeyCode() == KeyEvent.VK_CONTROL || e.getKeyCode() == KeyEvent.VK_SHIFT || e.getKeyCode() == KeyEvent.VK_ALT) {
                    lastKeyCode = 0;
                }
            }
        });

        addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                if (e.getButton() == MouseEvent.BUTTON1) {
                    LayoutPart layoutPart = getLayoutPart((int)e.getPoint().getX(), (int)e.getPoint().getY());
                    if (layoutPart != null) {
                        switch (layoutPart.partType) {
                            case BAR_PART : ActionController.selectAndNotePart(layoutPart.drumIndex, layoutPart.partIndex); break;
                            case DOWNBEAT_PART : ActionController.selectAndNoteDownbeatPart(layoutPart.partIndex); break;
                            case LOUDNESS_PART : ActionController.selectDrumLoudness(layoutPart.drumIndex, layoutPart.partIndex); break;
                            case STYLE_PART : ActionController.selectStyle(layoutPart.partIndex); break;
                        }
                    }
                    
                }

                requestFocus();
            }
        });

        setFocusable(true);

        paintLayout();
    }

    private final int BAR_PART = 1;
    private final int DOWNBEAT_PART = 2;
    private final int LOUDNESS_PART = 3;
    private final int STYLE_PART = 4;

    private class LayoutPart {
        Integer partType;
        Integer drumIndex;
        Integer partIndex;

        private LayoutPart(Integer drumIndex, Integer partIndex, Integer partType) {
            this.drumIndex = drumIndex;
            this.partIndex = partIndex;
            this.partType = partType;
        }
    }

    public LayoutPart getLayoutPart(Integer x, Integer y) {
        Song song = MainFrame.getSong();
        for (int i = 0; i < song.getTrackCount(); i++) {
            for (int j = 0; j < song.getBar(i, currentBar).getPartCount(); j++) {
                Rectangle2D rect = getPartRect(false, currentBar, j, i, false);
                if (x >= rect.getMinX() && x < rect.getMaxX() && y >= rect.getMinY() && y < rect.getMaxY()) {
                    return new LayoutPart(i, j, BAR_PART);
                }
            }
        }
        for (int j = 0; j < song.getDownbeat(currentBar).getPartCount(); j++) {
            Rectangle2D rect = getPartRect(true, currentBar, j, -1, false);
            if (x >= rect.getMinX() && x < rect.getMaxX() && y >= rect.getMinY() && y < rect.getMaxY()) {
                return new LayoutPart(-1, j, DOWNBEAT_PART);
            }
        }
        for (int i = 0; i < song.getTrackCount(); i++) {
            for (int j = 0; j < Options.DOWNBEAT_COUNT; j++) {
                Rectangle2D rect = getLoudnessRect(i, j, true);
                if (x >= rect.getMinX() && x < rect.getMaxX() && y >= rect.getMinY() && y < rect.getMaxY()) {
                    return new LayoutPart(i, j, LOUDNESS_PART);
                }
            }
        }
        for (int i = 0; i < song.getTrack(currentDrum).getDrum().getPosition(song.getTrack(currentDrum).getPosition()).getStyleCount(); i++) {
            Rectangle2D rect = getStyleRect(i, false);
            if (x >= rect.getMinX() && x < rect.getMaxX() && y >= rect.getMinY() && y < rect.getMaxY()) {
                return new LayoutPart(-1, i, STYLE_PART);
            }
        }
        return null;
    }

    private Rectangle2D getPartRect(Boolean downbeat, Integer barIndex, Integer partIndex, Integer drumIndex, Boolean fill) {
        Rectangle2D rect;
        Song song = MainFrame.getSong();
        Integer x1, y1, dx, dy;
        if (!downbeat) {
            x1 = 10 + 9*RECT_SIZE + song.getBar(drumIndex, barIndex).getRelativeTimeShift(partIndex) * RECT_SIZE * 4 / Options.RESOLUTION;
            y1 = drumIndex*RECT_SIZE;
            dx = (song.getBar(drumIndex, barIndex).getDuration(partIndex)) * RECT_SIZE * 4 / Options.RESOLUTION;
            dy = RECT_SIZE;
        }
        else {
            x1 = 10 + 9*RECT_SIZE + song.getDownbeat(barIndex).getRelativeTimeShift(partIndex) * RECT_SIZE * 4 / Options.RESOLUTION;
            y1 = song.getTrackCount()*RECT_SIZE + 10;
            dx = (song.getDownbeat(barIndex).getDuration(partIndex)) * RECT_SIZE * 4 / Options.RESOLUTION;
            dy = RECT_SIZE;
        }
        if (!fill) {
            rect = new Rectangle2D.Double(x1, y1, dx, dy);
        }
        else {
            rect = new Rectangle2D.Double(x1 + 2, y1 + 2, dx - 3, dy - 3);
        }
        return rect;
    }

    private Rectangle2D getLoudnessRect(Integer drumIndex, Integer level, Boolean extended) {
        Rectangle2D rect;
        Integer x1, y1, dx, dy;
        x1 = 9*RECT_SIZE + 2;
        y1 = drumIndex*RECT_SIZE + 3;
        dx = 6;
        dy = RECT_SIZE - 2;
        Integer deltaY = dy / Options.DOWNBEAT_COUNT;
        if (!extended) {
            rect = new Rectangle2D.Double(x1, y1 + (Options.DOWNBEAT_COUNT - 1 - level)*deltaY, dx, deltaY - 2);
        }
        else {
            rect = new Rectangle2D.Double(x1 - 1, y1 + (Options.DOWNBEAT_COUNT - 1 - level)*deltaY - 1, dx + 2, deltaY);
        }
        return rect;
    }

    private Rectangle2D getStyleRect(Integer level, Boolean fill) {
        Rectangle2D rect;
        Song song = MainFrame.getSong();
        Integer x1, y1, dx, dy;
        x1 = 0;
        y1 = (song.getTrackCount() + 1 + level)*RECT_SIZE;

        dx = 200;
        dy = RECT_SIZE;
        if (!fill) {
            rect = new Rectangle2D.Double(x1, y1, dx, dy);
        }
        else {
            rect = new Rectangle2D.Double(x1 + 1, y1 + 1, dx - 1, dy);
        }
        return rect;
    }

    public void paintLayout() {
        Song song = MainFrame.getSong();
        Graphics2D g2 = (Graphics2D)layoutImage.getGraphics();
        Rectangle2D rect;

        // Erasing previous image
        rect = new Rectangle2D.Double(0, 0, this.getWidth(), this.getHeight());
        g2.setPaint(Color.BLACK);
        g2.fill(rect);

        // Previous index of drum (e.g. crash, ride, china)
        Integer prevIndex = -1;
        for (int i = 0; i < song.getTrackCount(); i++) {
            // Rect for drum name
            rect = new Rectangle2D.Double(0*RECT_SIZE, i*RECT_SIZE, RECT_SIZE*6, RECT_SIZE);
            g2.setPaint(new Color(Color.GRAY.getRGB() - 0x444444));
            g2.draw(rect);

            // Rect for drum position
            rect = new Rectangle2D.Double(6*RECT_SIZE, i*RECT_SIZE, RECT_SIZE*3, RECT_SIZE);
            g2.setPaint(new Color(Color.GRAY.getRGB() - 0x444444));
            g2.draw(rect);

            // If new drum, then paint its name
            if (!prevIndex.equals(song.getTrack(i).getDrum().getIndex())) {
                g2.setFont(new Font("Verdana", Font.BOLD, 18));
                g2.setPaint(DRUM_COLORS[song.getTrack(i).getDrum().getIndex()]);
                g2.drawString(song.getTrack(i).getDrum().getName(), 3, (i + 1)*RECT_SIZE - 5);
            }
            prevIndex = song.getTrack(i).getDrum().getIndex(); 

            // Paint drum position
            g2.setFont(new Font("Verdana", Font.PLAIN, 12));
            g2.setPaint(DRUM_COLORS[song.getTrack(i).getDrum().getIndex()]);
            g2.drawString(song.getTrack(i).getDrum().getPosition(song.getTrack(i).getPosition()).getName(), RECT_SIZE*6 + 6, (i + 1)*RECT_SIZE - 5);

            // Paint bar parts
            for (int j = 0; j < song.getBar(i, currentBar).getPartCount(); j++) {
                // Paint part rect
                rect = getPartRect(false, currentBar, j, i, false);
                g2.setPaint(Color.GRAY);
                g2.draw(rect);

                // Filling part rect if note exists
                if (song.getBar(i, currentBar).getPart(j).getNote()) {
                    rect = getPartRect(false, currentBar, j, i, true);
                    Integer drumIndex = song.getTrack(i).getDrum().getIndex();
                    Integer styleIndex = song.getBar(i, currentBar).getPart(j).getStyle() * (STYLE_COLORS.length / song.getTrack(i).getDrum().getPosition(song.getTrack(i).getPosition()).getStyleCount());
                    g2.setPaint(new GradientPaint((float)rect.getMinX(), (float)rect.getMinY(), STYLE_COLORS[styleIndex], (float)rect.getMaxX(), (float)rect.getMaxY(), DRUM_COLORS[drumIndex]));
                    g2.fill(rect);
                }
            }

            // Paint loudness
            Color fillColor = i % 2 == 0 ? Color.YELLOW : Color.RED;
            for (int j = 0; j < Options.DOWNBEAT_COUNT; j++) {
                rect = getLoudnessRect(i, j, false);
                g2.setPaint(song.getBar(i, currentBar).getLoudness() >= j ? fillColor : Color.BLACK);
                g2.fill(rect);
            }
        }

        // Paint downbeat bar
        for (int j = 0; j < song.getDownbeat(currentBar).getPartCount(); j++) {
            // Paint part rect
            rect = getPartRect(true, currentBar, j, -1, false);
            g2.setPaint(Color.GRAY);
            g2.draw(rect);

            // Filling part rect if note exists
            if (song.getDownbeat(currentBar).getPart(j).getNote()) {
                rect = getPartRect(true, currentBar, j, -1, true);
                g2.setPaint(new GradientPaint((float)rect.getMinX(), (float)rect.getMinY(), STYLE_COLORS[0], (float)rect.getMaxX(), (float)rect.getMaxY(), DRUM_COLORS[DRUM_COLORS.length - 1]));
                g2.fill(rect);
            }
        }

        // Paint selected part (downbeat or regular bar)
        if (ActionController.getDownbeatSelected()) {
            rect = getPartRect(true, currentBar, currentPart, -1, false);
        }
        else {
            rect = getPartRect(false, currentBar, currentPart, currentDrum, false);
        }
        g2.setPaint(Color.WHITE);
        g2.draw(rect);

        if (!ActionController.getDownbeatSelected()) {
            // Paint playing styles
            for (int i = 0; i < song.getTrack(currentDrum).getDrum().getPosition(song.getTrack(currentDrum).getPosition()).getStyleCount(); i++) {
                rect = getStyleRect(i, true);
                Color color1 = STYLE_COLORS[i * (STYLE_COLORS.length / song.getTrack(currentDrum).getDrum().getPosition(song.getTrack(currentDrum).getPosition()).getStyleCount())];
                Color color2 = DRUM_COLORS[song.getTrack(currentDrum).getDrum().getIndex()];
                g2.setPaint(new GradientPaint((float)rect.getMinX(), (float)rect.getMinY(), color1, (float)rect.getMaxX(), (float)rect.getMaxY(), color2));
                g2.fill(rect);
                Integer curStyle = song.getBar(currentDrum, currentBar).getPart(currentPart).getStyle();
                if (i == curStyle) {
                    g2.setFont(new Font("Verdana", Font.BOLD, 12));
                }
                else {
                    g2.setFont(new Font("Verdana", Font.PLAIN, 12));
                }
                g2.setPaint(Color.BLACK);
                g2.drawString(song.getTrack(currentDrum).getDrum().getPosition(song.getTrack(currentDrum).getPosition()).getStyle(i).getName(), (int)rect.getMinX() + 102, (int)rect.getMaxY() - 4);
            }

            // Paint selected style
            Integer curStyle = song.getTrackCount() + 1 + song.getBar(currentDrum, currentBar).getPart(currentPart).getStyle();
            rect = getStyleRect(curStyle, false);
            g2.setPaint(Color.WHITE);
            g2.draw(rect);
        }

        // Updating east panel values
        MainFrame.getEastPanel().getCurrentBar().setText("" + (currentBar + 1) + " of " + MainFrame.getSong().getBarCount() + "");
        MainFrame.getEastPanel().getTempoField().setText(MainFrame.getSong().getBar(currentDrum, currentBar).getTempo().toString());
        MainFrame.getEastPanel().getBeatNumber().setText(MainFrame.getSong().getBar(currentDrum, currentBar).getTimeSignature().getBeatNumber().toString());
        MainFrame.getEastPanel().getBeatUnit().setText(MainFrame.getSong().getBar(currentDrum, currentBar).getTimeSignature().getFullBeatUnit().toString());
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D)g;

        g2.drawImage(layoutImage, 0, 0, null);
    }
}
